// <!-- https://juejin.cn/post/6844903778974597134#heading-0 排序动画 -->

var arr = [4, 1, 7, 10, 2, 50, 0, 5]

/* TODO 1 选择排序  能理解*/
// 第一个和第二个以后所有比较,找出最小的索引，最后交换位置，第二个和第三个以后所有比较，找出最小的，最后交换位置
function xuanzhe(arr) {
	let length = arr.length
	if (length <= 1) return
	for (let i = 0; i < length - 1; i++) {
		let minIndex = i
		for (let j = i + 1; j < length; j++) {
			if (arr[minIndex] > arr[j]) {
				minIndex = j
			}
		}
		;[arr[minIndex], arr[i]] = [arr[i], arr[minIndex]]
	}
	return arr
}
// console.log(xuanzhe(arr));

/* TODO 2 冒泡排序  能理解*/
// 前后两两进行比较，如果符合比较的条件，就交换两个数的位置。https://www.cnblogs.com/taohuaya/p/9543065.html
//  https://github.com/biaochenxuying/blog/issues/39
// 加标识 isOk 可优化
function maopao(arr) {
	// let isOk = true
	let length = arr.length
	if (length <= 1) return
	//i < length - 1 是因为外层只需要 length-1 次就排好了，第 length 次比较是多余的。
	for (let i = 0; i < length - 1; i++) {
		// j < length - i - 1 是因为内层的 length-i-1 到 length-1 的位置已经排好了，不需要再比较一次。
		// FIXME 重点，每次从0开始
		for (let j = 0; j < length - 1 - i; j++) {
			if (arr[j] > arr[j + 1]) {
				;[arr[j + 1], arr[j]] = [arr[j], arr[j + 1]]
				// isOk = false
			}
		}
		// if(isOk) break
	}
	return arr
}
// console.log(maopao(arr));

/* TODO 3 快速排序 能理解*/
// 选择一个数，将小于次数的放在左边，大于的放在右边，递归循环
function quickSort(arr) {
	if (arr.length <= 1) {
		//如果数组只有一个数，就直接返回；
		return arr
	}
	const num = Math.floor(arr.length / 2) //找到中间数的索引值，如果是浮点数，则向下取整
	const centerValue = arr.splice(num, 1)[0] //找到中间数的值  ,需要从原数组删除，避免出现死循环
	const left = []
	const right = []
	for (var i = 0; i < arr.length; i++) {
		if (arr[i] < centerValue) {
			left.push(arr[i]) // 小于centerValue小的数传到左边数组
		} else {
			right.push(arr[i]) // 大于等于centerValue的数传到左边数组
		}
	}
	return [...quickSort(left), centerValue, ...quickSort(right)] //递归不断重复比较
}
console.log(quickSort(arr))

/* FIXME 4 插入排序 能理解 手写有点不熟悉*/ //github.com/biaochenxuying/blog/issues/39
// 1 从第一个默认被排好序的元素开始
// 2 取出下一个元素，在已经排序的元素序列中从后向前扫描
// 3 如果已排序的元素大于取出的元素，则将其分别向后移动一位
// 4 重复步骤 3,直到找到已排序的元素中小于或等于取出的元素，
// 5 将取出的元素放到它的后一位
// 6 重复步骤 2-5
function insertionSort(arr) {
	const len = arr.length
	for (let i = 1; i < len; i++) {
		let preIndex = i - 1 // 待比较元素的下标
		let current = arr[i] // 当前元素
		while (preIndex >= 0 && arr[preIndex] > current) {
			arr[preIndex + 1] = arr[preIndex] //将待比较元素后移一位
			preIndex-- //游标前移一位
		}
		if (preIndex + 1 != i) {
			//避免同一个元素赋值给自身
			arr[preIndex + 1] = current //将当前元素插入预留空位
		}
	}
	return arr
}
console.log(insertionSort([5, 4, 3, 2, 1]))
/*
		var arr  = [4, 3, 6, 2]

		第1次循环
				flag = 3 ;  preIndex = 0
				第1次比较    preIndex >= 0 && arr[preIndex]=4   >= flag   》 arr[preIndex+1] = arr[preIndex]     》 [4,4,6,2]
				第2次比较    preIndex-- < 0                               》 arr[preIndex+1] = flag              》 [3,4,6,2]
		第2次循环
				flag = 6 ;  preIndex = 1
				第1次比较    preIndex >=0 && arr[preIndex]=4 < flag       》 arr[preIndex+1] = flag              》 [3,4,6,2]
		第3次循环
				flag = 2 ;  preIndex = 2
				第1次比较    preIndex >=0 && arr[preIndex]=6 > flag       》 arr[preIndex+1] = arr[preIndex]     》 [3,4,6,6]
				第2次比较    preIndex-- >= 0 && arr[preIndex]=4 > flag    》 arr[preIndex+1] = arr[preIndex]     》 [3,3,4,6]
				第3次比较    preIndex-- < 0                               》 arr[preIndex+1] = flag              》 [2,3,4,6]

 */

/* TODO 5 归并排序 能理解 */
// 排序一个数组，我们先把数组递归的从中间分成前后两部分，然后对前后两部分分别排序，再将排好序的两部分,最后排序合并在一起，这样整个数组就都有序了。
function mergeSort(arr) {
	if (arr.length < 2) return arr
	const middle = Math.floor(arr.length / 2)
	// 拆分为两个子数组
	const left = arr.slice(0, middle)
	const right = arr.slice(middle)
	// 排序合并返回
	return merge(mergeSort(left), mergeSort(right))
}

// left 和 right 已经排序好了
function merge(left, right) {
	const result = []
	while (left.length && right.length) {
		// 注意: 判断的条件是小于或等于，如果只是小于，那么排序将不稳定.
		if (left[0] <= right[0]) {
			result.push(left.shift())
		} else {
			result.push(right.shift())
		}
	}

	return result.concat(left, right)
}
// console.log(mergeSort(arr));
/*
	例如
	//mergeSort
													[1,3,5,7,9,2,4,6,8]
	第一步分为        [1,3,5,7]              [9,2,4,6,8]
	第二步分为      [1,3]    [5,7]         [9,2]    [4,6,8]
	第三步分为     [1] [3]  [5] [7]       [9] [2]  [4]  [6,8]
	第四步分为                                         [6] [8]

	// merge
	第五步返回                                          [6,8]
	第六步返回      [1,3]    [5,7]         [2,9]    [4,6,8]
	第七步返回          [1,3,5,7]            [2,4,6,8,9]
	第八步返回                [1,2,3,4,5,6,7,8,9]

	*/

// TODO 希尔排序 https://github.com/biaochenxuying/blog/issues/40
const shellSort = arr => {
	let len = arr.length,
		temp,
		gap = 1
	while (gap < len / 3) {
		//动态定义间隔序列
		gap = gap * 3 + 1
	}
	for (gap; gap > 0; gap = Math.floor(gap / 3)) {
		for (let i = gap; i < len; i++) {
			temp = arr[i]
			let j = i - gap
			for (; j >= 0 && arr[j] > temp; j -= gap) {
				arr[j + gap] = arr[j]
			}
			arr[j + gap] = temp
			console.log('arr  :', arr)
		}
	}
	return arr
}

// TODO 堆排序  https://github.com/biaochenxuying/blog/issues/40
const heapSort = array => {
	console.time('堆排序耗时')
	// 初始化大顶堆，从第一个非叶子结点开始
	for (let i = Math.floor(array.length / 2 - 1); i >= 0; i--) {
		heapify(array, i, array.length)
	}
	// 排序，每一次 for 循环找出一个当前最大值，数组长度减一
	for (let i = Math.floor(array.length - 1); i > 0; i--) {
		// 根节点与最后一个节点交换
		swap(array, 0, i)
		// 从根节点开始调整，并且最后一个结点已经为当前最大值，不需要再参与比较，所以第三个参数为 i，即比较到最后一个结点前一个即可
		heapify(array, 0, i)
	}
	console.timeEnd('堆排序耗时')
	return array
}

// 交换两个节点
const swap = (array, i, j) => {
	let temp = array[i]
	array[i] = array[j]
	array[j] = temp
}

// 将 i 结点以下的堆整理为大顶堆，注意这一步实现的基础实际上是：
// 假设结点 i 以下的子堆已经是一个大顶堆，heapify 函数实现的
// 功能是实际上是：找到 结点 i 在包括结点 i 的堆中的正确位置。
// 后面将写一个 for 循环，从第一个非叶子结点开始，对每一个非叶子结点
// 都执行 heapify 操作，所以就满足了结点 i 以下的子堆已经是一大顶堆
const heapify = (array, i, length) => {
	let temp = array[i] // 当前父节点
	// j < length 的目的是对结点 i 以下的结点全部做顺序调整
	for (let j = 2 * i + 1; j < length; j = 2 * j + 1) {
		temp = array[i] // 将 array[i] 取出，整个过程相当于找到 array[i] 应处于的位置
		if (j + 1 < length && array[j] < array[j + 1]) {
			j++ // 找到两个孩子中较大的一个，再与父节点比较
		}
		if (temp < array[j]) {
			swap(array, i, j) // 如果父节点小于子节点:交换；否则跳出
			i = j // 交换后，temp 的下标变为 j
		} else {
			break
		}
	}
}
